/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package controller;

import digitrencer.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.Timer;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import view.GeneratorJDialog;
import view.MainJFrame;             //View miatt
import view.OscilloscopeJDialog;
import view.View;

/**
 * A modell kezelését végző osztály. Létrehozza a modellt, és megteremti a
 * kapcsolatot a felhasználóval.
 * @author Brute Force
 */
public class Controller {
    /**
     * A Model referenciája.
     */
    Model model;
    /**
     * A View referenciája.
     */
    View view;
    /**
     * A naplófájl.
     */
    PrintWriter logfile;
    /**
     * Az alkalmazás fő ablaka.
     */
    final MainJFrame mainJFrame;            //NetBeans akarta, hogy final legyen.
    /**
     * A hálózat bemeneteinek listája, a naplózáshoz szükséges.
     */
    ArrayList<Integer> inputs;
    /**
     * A hálózat kimeneteinek listája, a naplózáshoz szükséges.
     */
    ArrayList<Integer> outputs;
    /**
     * Az időzítő, amely felelős a jelgenerátorok automatikus léptetéséért.
     */
    javax.swing.Timer timer;                //Nem kéne a "javax.swing", mert csak ez a csomag van importálva, de így egyértelműbb.
    static final int timerInterval = 750;

    /**
     * A program belépési pontja, létrehozza a Controllert, és elindítja a fő ciklust.
     * @param args A parncssori paraméterek.
     */
    public static void main(String[] args) {
        Controller controller = new Controller();
    }

    /**
     * Elvégzi a program inicializálását, létrehozza a timert, a főablakot és kezeli a timer
     * által küldött jelzéseket.
     */
    private Controller() {                                         //nem kell throws, mivel itt mindent el is kapunk
        try {
            inputs = new ArrayList<Integer>();
            outputs = new ArrayList<Integer>();
            logfile = null;                                                     //Alapból nem tudjuk hova kell naplózni, ha kell.
            timer = new Timer(timerInterval, new ActionListener() {                      //Kis inline eseménykezelő osztály. | 750==750 milisec

                @Override
                public void actionPerformed(ActionEvent e) {
                    step();
                }
            });

        } catch (SecurityException ex) {                 //from PrintWriter
            printMessage("EXCEPTION: " + ex.getMessage() + "\n Jogosultsági hiba! A program leáll!");
            JOptionPane.showMessageDialog(null, ex.getMessage() + "\n Jogosultsági hiba! A program leáll!", "Kritikus hiba", JOptionPane.ERROR_MESSAGE);
            System.exit(-1);
            //ex.printStackTrace();
        } catch (Exception ex) {
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Kritikus hiba", JOptionPane.ERROR_MESSAGE);
            System.exit(-1);
            //ex.printStackTrace();
        } finally {     //Mindenképpen létre kell hoznunk a fő ablakot, különben nem tudunk kommunikálni a felhasználóval.
            mainJFrame = new MainJFrame(this);         //A NetBeans akarta hogy final legyem.
            java.awt.EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    mainJFrame.setVisible(true);
                }
            });
        }
    }

    /**
     * Kiír egy üzenetet a logfile-ba, amennyiben az létezik.
     * @param message A kiírandó üzenet.
     */
    private void printMessage(String message) {
        if (logfile != null) {
            logfile.println(message);
        }
    }

    /**
     * Kilistázza a rendszer be- és kimeneteinek értékét.
     * @throws IllegalArgumentException Csak hibás működés esetén fordulhat elő,
     * a getInputValue és a getOutputValue dobhatja.
     */
    private void printIO() throws IllegalArgumentException {     //from getInputValue OR getOutputValue
        String temp;
        for (Integer i : inputs) {                                //bemenetek
            for (boolean b : model.getInputValue(i)) {            //mert ugyebár nem sima booelan-t hanem egy booelan tömböt ad vissza a getInputValue
                if (b) {
                    temp = "1";                              //A boolean értéke kiírva alapesetben "true" és "false" nem pedig "0" és "1"
                } else {
                    temp = "0";
                }
                printMessage("Input #" + i + "\t:\t" + temp);
            }
        }
        for (Integer i : outputs) {                               //kimenetek
            for (boolean b : model.getOutputValue(i)) {           //mert ugyebár nem sima booelan-t hanem egy booelan tömböt ad vissza a getInputValue
                if (b) {
                    temp = "1";                              //A boolean értéke kiírva alapesetben "true" és "false" nem pedig "0" és "1"
                } else {
                    temp = "0";
                }
                printMessage("Output #" + i + "\t:\t" + temp);
            }
        }
    }

    /**
     * A naplófájl betöltését végző függvény, ami a fájlválasztó ablakért
     * és a naplózás menüpont melletti pipa állapotáért is felel.
     * @param logCheckBox A naplózás menüpont melletti pipa.
     */
    public void setLogFile(JCheckBoxMenuItem logCheckBox) {
        JFileChooser fc = new JFileChooser(new File(".").getAbsolutePath());
        FileNameExtensionFilter filter = new FileNameExtensionFilter("TXT fájlok", "txt");
        fc.setFileFilter(filter);
        if (logCheckBox.getState()) {
            int returnVal = fc.showDialog(mainJFrame, "Naplózás");              //Fájlválasztó ablak feldobása.
            if (returnVal == JFileChooser.CANCEL_OPTION) {
                logCheckBox.setState(false);
            }
            if (returnVal == JFileChooser.ERROR_OPTION) {                       //Nem sikerült a fájl betöltése.
                JOptionPane.showMessageDialog(mainJFrame,
                        "A kiválasztott naplófájl betöltése nem sikerült.",
                        "Fájlbetöltési hiba",
                        JOptionPane.ERROR_MESSAGE);
            }
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                try {
                    logfile = new PrintWriter(fc.getSelectedFile());
                    printMessage("A Digitálisrendszer nevű program  kimenetének naplófájlja.");
                    printMessage("");
                } catch (FileNotFoundException ex) {
                    logfile = null;
                    logCheckBox.setState(false);
                    printMessage("EXCEPTION: Fájl nem található! ("+ex.getMessage()+")");
                    JOptionPane.showMessageDialog(null, "Fájl nem található!", "Fájlbetöltési hiba", JOptionPane.ERROR_MESSAGE);
                            }
            }
        } else {            //Kivettem a pipát.
            if (logfile != null) {          //Azért kell ez a vizsgálat, mert a java észreveszi, amikor a [if (logCheckBox.getState())]-en belül átállítom a logCheckBox értékét
                logfile.close();            //false-ra [logCheckBox.setState(false);] és visszaugtik a [if (logCheckBox.getState())] vizsgálathoz és ekkor kerülhetünk az else ágba úgy hogy a logfile null.
            }
            logfile = null;
        }
    }

    //by Kéki

    /**
     * Grafikus vezérléshez.
     * Betölti az XML file-t, amit az itt megjelenített fájlválasztó dialógusablakban válaszott kiválasztott a felhasználó
     */
    public void loadXML() {
        try {
            JFileChooser fc = new JFileChooser(new File(".").getAbsolutePath());    //fájlválasztó objektum léterhozása
            FileNameExtensionFilter filter = new FileNameExtensionFilter("XML fájlok", "xml");
            fc.setFileFilter(filter);
            int returnVal = fc.showOpenDialog(mainJFrame);                          //fájlválasztó megjelenítése
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                printMessage("> load " + fc.getSelectedFile().toString());
                ArrayList<Integer> tempInputs = new ArrayList<Integer>();
                ArrayList<Integer> tempOutputs = new ArrayList<Integer>();
                View tempView = new View(this);
                XMLParser xmlParser = new XMLParser(tempView, tempInputs, tempOutputs, logfile);              //Minden betöltéshez új XMLParser kell.
                model = xmlParser.LoadXML(fc.getSelectedFile());                    //sikeres fájlválasztás esetén fájl betöltése
                // ha eljut ide a hívás, akkor sikerrel jártunk
                view = tempView;
                mainJFrame.setView(view);
                inputs = tempInputs;
                outputs = tempOutputs;
                printMessage("ÚJ HÁLÓZAT BETÖLTVE.");
            }                                                                           //sikeres fájlválasztás != még nem biztos hogy helye a választott fájl
            if (returnVal == JFileChooser.ERROR_OPTION) {
                JOptionPane.showMessageDialog(mainJFrame, "A fájl kiválasztása közben hiba történt.\n"
                        + "A fálj kiválasztása nem sikerült.",
                        "Sikertelen fájlkiválasztás",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (ParserConfigurationException ex) {         //XMLPaeser konstruktora
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Kritikus hiba", JOptionPane.ERROR_MESSAGE);
        } catch (SAXException ex) { //dobunk még ilyet? - IGEN :)
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Bemeneti fájl hiba", JOptionPane.ERROR_MESSAGE);
            //ex.printStackTrace();
        } catch (IOException ex) { //dobunk még ilyet?
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Bemeneti fájl hiba", JOptionPane.ERROR_MESSAGE);
            //ex.printStackTrace();
        } catch (NoSuchFieldException ex) {
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Bemeneti fájl hiba", JOptionPane.ERROR_MESSAGE);
            //ex.printStackTrace();
        } catch (IllegalArgumentException ex) {
            printMessage("EXCEPTION: " + ex.getMessage());
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Bemeneti fájl hiba", JOptionPane.ERROR_MESSAGE);
            //ex.printStackTrace();
        } finally {
            mainJFrame.repaint();
        }
    }

    //by Ádám & override by Kéki

    /**
     * Beállítja a setSequence parancs paramétereként megadott ID-jű jelgenerátor
     * szekvenciáját a megadott értékre. Ehhez a felhasználói interakció miatt létrehoz egy
     * GeneratorJDialog ablakot.
     * Egyben a lekérdezést is tartalmazza a jóváhagyás miatt, mivel az alap seq-et írjuk át.
     * @param ID A beállítandó generátor ID-je.
     */
    public void setSequence(int ID) {
        try {
            if (model == null) {        //Ennek nem sok értelme van, mert ahhoz hogy ez meghívódjon rá kell tudni kattintai egy generátorra,
                //ami meg csak akkor van ha be van töltve hálózat. Konzolos résznél volt értelme.
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            boolean isPlay = timer.isRunning();
            if (isPlay) {                                                       //Ha redetetileg futott, megállítom a timert.
                timer.stop();
            }
            String sequence = GeneratorJDialog.showDialog(mainJFrame, model.getSequence(ID));   //megjelenítjük a generator sorozatat
            model.setSequence(ID,sequence);                                                     //beállítjuk az új szekvenciát
            model.run();    //frissítjuk a rendszert
            printMessage("> setSequence " + String.valueOf(ID)+ "\t:\t" + sequence);
            printIO();                                  //throws IllegalArgumentException - nem ezét kapom el, hanem a generátor beállításáét
            if (isPlay) {                                                        //Ha eredetileg futott, újraindítom a timert.
                timer.start();
            }
        } catch (IllegalArgumentException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Hibás paraméter", JOptionPane.ERROR_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } catch (UnstableNetworkException ex) {
            timer.stop();
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Instabil hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } finally {
            mainJFrame.repaint();
        }
    }

    //by Gergő - Ádám kicserelt egy parametert:D:D

    /**
     * Átbillenti a setSwitch paramétereként megadott kapcsolót.
     * @param ID Az átbillentendő kapcsoló ID-je.
     */
    public void setSwitch(int ID) {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            model.setSwitch(ID);    //a kapcsolo allapotat atvaltjuk
            model.run();    //frissítjuk a halozatot
            printMessage("> setSwitch " + String.valueOf(ID));
            printIO();                                          //throws IllegalArgumentException
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } catch (UnstableNetworkException ex) {
            timer.stop();
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Instabil hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } finally {
            mainJFrame.repaint();
        }
    }

    /**
     * Visszaállítja a rendszert az alapállapotba.
     */
    public void reset() {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            timer.stop();
            model.reset();
            printMessage("> reset");
            printIO();                                          //throws IllegalArgumentException
        } catch (UnsupportedOperationException ex) {
            //Nincs még model.
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
        } finally {
            mainJFrame.repaint();
        }
    }

    /**
     * Lépteti a rendszerben található összes jelgenerátort.
     */
    public void step() {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            model.stepGenerators();
            model.run();
            printMessage("> step");
            printIO();                                          //throws IllegalArgumentException
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            //Nincs még model.
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } catch (UnstableNetworkException ex) {
            timer.stop();
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Instabil hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        } finally {
            mainJFrame.repaint();
        }
    }

    /**
     * Elindítja a timert és ezzel a generátorok léptetését.
     */
    public void play() {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            timer.start();
        } catch (UnsupportedOperationException ex) {
            //Nincs még model.
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
        }
    }

    /**
     * Megállítja a timert, ezzel szünetelteti a generátorok léptetését.
     */
    public void pause() {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            timer.stop();
        } catch (UnsupportedOperationException ex) {
            //Nincs még model.
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
        }
    }

    //by Gergő - egy az egyben a 2 utána következő
    //Ádám belefirkált

    /**
     * Lekérdezi a getOsciloscopeHistory parancs paramétereként megadott
     * oszcilloszkóp által tárolt jelsorozatot, amit megjelenít egy OscilloscopeJDialogban.
     * Emellett megállítja a timert is.
     * @param ID A lekérdezni kívánt oszcilloszkóp ID-je.
     */
    public void getOscilloscopeHistory(int ID) {
        try {
            if (model == null) {                //Ennek nem sok értelme van, mert ahhoz hogy ez meghívódjon rá kell tudni kattintai egy generátorra,
                //ami meg csak akkor van ha be van töltve hálózat. Konzolos résznél volt értelme.
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            boolean isPlay = timer.isRunning();
            if (isPlay) {                                                       //Ha redetetileg futott, megállítom a timert.
                timer.stop();
            }
            OscilloscopeJDialog.showDialog(mainJFrame, model.getOscilloscopeHistory(ID));
            if (isPlay) {                                                        //Ha eredetileg futott, újraindítom a timert.
                timer.start();
            }
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
        }
    }

    //by Gergő - szinte egy az egyben a getInputValue másolata
    //by Ádám - Használd egészséggel Dani

    /**
     * Lekérdezi a getOutputValue parancs paramétereként megadott kimenet értékét.
     * @param ID A lekérdezni kívánt kimenet ID-je.
     * @return Ha van betöltött hálózat, akkor visszaadja a kívánt értéket, egyébként null értéket ad vissza.
     */
    public boolean[] getOutputValue(int ID) {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            boolean[] value = model.getOutputValue(ID); //sima lekerdezes
            return value;
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace
            return null;    // ez normális esetben nem is fordulhatna elő, de a NetBeans-nek kell return
        }
    }

    // by Ádám - Ilyet se láttatok még kiírva sehova...:D

    /**
     * Lekérdezi a getInputValue parancs paramétereként megadott bemenet értékét.
     * Működése hasonló a getOutputValue-éhoz.
     * @param ID A lekérdezni kívánt bemenet ID-je.
     * @return Ha van betöltött hálózat, akkor visszaadja a kívánt értéket, egyébként null értéket ad vissza.
     */
    public boolean[] getInputValue(int ID) {
        try {
            if (model == null) {
                throw new UnsupportedOperationException("Amíg nincs hálózat betöltve, ez a funkció nem támogatott!");
            }
            boolean[] value = model.getInputValue(ID); //1 egyszeru lekerdezes
            return value;
        } catch (UnsupportedOperationException ex) {
            JOptionPane.showMessageDialog(mainJFrame, ex.getMessage(), "Nincs hálózat", JOptionPane.WARNING_MESSAGE);
            printMessage("EXCEPTION: " + ex.getMessage());
            //ex.printStackTrace();
            return null;    // ez normális esetben nem is fordulhatna elő, de a NetBeans-nek kell return
        }
    }

    //by Kéki - Azért kell, hogy biztonságosan be tudjuk zárni a programot, mert ha csak simán kilépünk, akkor a megnyitott fájljaink nem záródnak le helyesen,
    //ami addig nem gond, amíg nincs olyan fájl, amiben írunk, vagyis nem naplózunk.
    
    /**
     * A program helyes bezárásáért felelős függvény, ami naplózás esetén kilépéskor bezárja a logfájlt.
     */
    public void exit() {
        printMessage("> exit");
        try {
            if (logfile != null) {
                logfile.close();
            }
        } finally {
            System.exit(0);
        }
    }
}
